/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Misc functions of id5db
 * 
 * Includes:
 * MySQL operator
 * Text logger
 * 
 * and so on...
 * 
 * ADDITIONAL FILE OF id5db
 * By Dr.NP <np@bsgroup.rog>
 */

#include "memcached.h"
#include "id5db_misc.h"
#include "id5db_queue.h"
#include "id5db_script.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>

#include <mysql.h>
#include <snappy-c.h>

#ifndef _POSIX_PATH_MAX
    #define _POSIX_PATH_MAX 1024
#endif

#ifndef _ISOC99_SOURCE
    #define _ISOC99_SOURCE
#endif

char *compress_buffer = NULL;
static size_t compressed_length;
static int log_fd = -1;

void id5db_misc_init()
{
    compressed_length = snappy_max_compressed_length(MMAP_FILESIZE);
    compress_buffer = calloc(1, compressed_length);
    if (!compress_buffer)
    {
        perror("Cannot malloc snappy buffer, check your free memory first.");
        exit(1);
    }
    
    char log_filename[_POSIX_PATH_MAX];
    snprintf(log_filename, _POSIX_PATH_MAX - 1, "%s", id5db_script_get_config("log_file"));
    log_fd = open(log_filename, O_RDWR | O_CREAT | O_APPEND, (mode_t) 0600);
    if (-1 == log_fd)
    {
        fprintf(stderr, "Cannot create/open log file %s\n", log_filename);
    }
    
    id5db_log("General - id5db server start");
    
    return;
}

MYSQL *db_hdl = NULL;
void id5db_mysql_init(const char *host, unsigned int port, const char *username, const char *password, const char *database)
{
    db_hdl = mysql_init(NULL);
    
    if (!db_hdl)
    {
        fprintf(stderr, "MySQL initialize error!\n");
        exit(1);
    }
    
    char *real_password = (char *) password;
    if (0 == strlen(real_password))
    {
        // mysql_real_connect cannot be overridden by an empty password
        real_password = NULL;
    }
    
    if (!mysql_real_connect(db_hdl, host, username, real_password, database, port, NULL, 0))
    {
        fprintf(stderr, "MySQL connection error\n");
        exit(1);
    }
    
    my_bool enable = 1;
    mysql_options(db_hdl, MYSQL_OPT_RECONNECT, &enable);
    
    id5db_log("DB - MySQL initialized");
    
    return;
}

int id5db_mysql_query(const char *query, size_t len)
{
    if (!db_hdl)
    {
        return -1;
    }
    
    int res = mysql_real_query(db_hdl, query, (unsigned long) len);
    if (0 != res)
    {
        id5db_log("DB - Query error : %s [QUERY] - %s", mysql_error(db_hdl), query);
    }
    
    return res;
}

uint64_t get_last_insert_id()
{
    if (!db_hdl)
    {
        // MySQL uninitialized
        return (uint64_t) 0xFFFFFFFFFFFFFFFF;
    }
    
    if (0 != mysql_query(db_hdl, id5db_script_get_config("last_id_query")))
    {
        id5db_log("DB - Get last id query error : %s", id5db_script_get_config("last_id_query"));
        return (uint64_t) 0xFFFFFFFFFFFFFFFF;
    }
    
    MYSQL_RES *res = mysql_store_result(db_hdl);
    if (!res)
    {
        id5db_log("DB - Get last id fetch result error");
        return (uint64_t) 0xFFFFFFFFFFFFFFFF;
    }
    
    MYSQL_ROW row = mysql_fetch_row(res);
    if (!row)
    {
        id5db_log("DB - Cannot get last id, maybe empty table here");
        return (uint64_t) 0xFFFFFFFFFFFFFFFF;
    }
    
    uint64_t id = strtoull(row[0], NULL, 10);
    id5db_log("DB - Get last queue id : %llu", id);
    
    return id;
}

void id5db_misc_chdir(void)
{
    char self_name[_POSIX_PATH_MAX];
    char *ret = NULL;
    char *curr;
    
    if (-1 == readlink("/proc/self/exe", self_name, _POSIX_PATH_MAX))
    {
        ret = "./";
    }
    
    else
    {
        ret = realpath(self_name, NULL);
        if (!ret)
        {
            return;
        }
        
        curr = strrchr(ret, '/');
        
        if (curr)
        {
            curr[0] = 0x0;
        }
    }
    
    if (0 != chdir(ret))
    {
        fprintf(stderr, "Cannot change directory to %s\n", ret);
        exit(1);
    }
    
    return;
}

void id5db_log(const char *fmt, ...)
{
    va_list ap;
    struct tm* loctime;
    static char data[LOG_LINE_LENGTH];
    static char line[LOG_LINE_LENGTH];
    static char tgdate[DATE_SIZE];
    size_t len;
    time_t now = time((time_t *) NULL);
    
    if (log_fd < 0)
    {
        return;
    }
    
    va_start(ap, fmt);
    vsnprintf(data, LOG_LINE_LENGTH - 2, fmt, ap);
    va_end(ap);
    
    loctime = localtime(&now);
    strftime(tgdate, DATE_SIZE, "%m/%d%Y %H:%M:%S", loctime);
    len = snprintf(line, LOG_LINE_LENGTH - 1, "[%s] : %s\n", tgdate, data);
    write(log_fd, line, len);
    fsync(log_fd);
    
    return;
}

static void * _real_compress(void *arg)
{
    struct snappy_compress_t *me = arg;
    struct stat st;
    
    if (!compress_buffer)
    {
        return NULL;
    }
    
    id5db_log("Snappy - Compress file %s => %s", me->src_filename, me->tgt_filename);
    // MMAP input first
    int fdi = open(me->src_filename, O_RDONLY, (mode_t) 0600);
    if (-1 == fdi)
    {
        // Cannot open file
        id5db_log("Snappy - Cannot open input file : %s", me->src_filename);
        return NULL;
    }
    
    stat(me->src_filename, &st);
    size_t filesize = st.st_size;
    if (filesize < MMAP_FILESIZE)
    {
        int res = lseek(fdi, MMAP_FILESIZE - 1, SEEK_SET);
        if (-1 == res)
        {
            // Resize error
            close(fdi);
            return NULL;
        }
        
        write(fdi, "", 1);
        fsync(fdi);
    }
    
    char *input = mmap(0, MMAP_FILESIZE, PROT_READ, MAP_SHARED, fdi, 0);
    size_t c_len = compressed_length;
    if (SNAPPY_OK == snappy_compress(input, MMAP_FILESIZE, compress_buffer, &c_len))
    {
        int fdo = open(me->tgt_filename, O_RDWR | O_CREAT, (mode_t) 0600);
        if (-1 == fdo)
        {
            // Create output file error
            id5db_log("Snappy - Cannot create output file : %s", me->tgt_filename);
        }
        
        else
        {
            // Write data to new file
            write(fdo, compress_buffer, c_len);
            fsync(fdo);
            close(fdo);
			
			// Delete source file
			// Modified by Dr.NP 04/27/2012
			if (me->src_filename)
			{
				unlink(me->src_filename);
			}
        }
    }
    
    close(fdi);
    
    if (me->src_filename)
    {
        free(me->src_filename);
    }
    
    if (me->tgt_filename)
    {
        free(me->tgt_filename);
    }
    
    free(me);
    
    return NULL;
}

void id5db_file_compress(const char *src_filename, const char *tgt_filename)
{
    pthread_t thread;
    pthread_attr_t attr;
    struct snappy_compress_t *arg = malloc(sizeof(struct snappy_compress_t));
    
    if (!arg || !src_filename || !tgt_filename)
    {
        return;
    }
    
    arg->src_filename = strdup(src_filename);
    arg->tgt_filename = strdup(tgt_filename);
    arg->delete_src = 1;
    
    pthread_attr_init(&attr);
    pthread_create(&thread, &attr, _real_compress, arg);
    
    return;
}
